/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.sql.rowset.spi;

import java.util.logging.*;
import java.util.*;

import java.sql.*;
import javax.sql.*;

import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;

import javax.naming.*;
import sun.reflect.misc.ReflectUtil;

/**
 * The Service Provider Interface (SPI) mechanism that generates <code>SyncProvider</code>
 * instances to be used by disconnected <code>RowSet</code> objects.
 * The <code>SyncProvider</code> instances in turn provide the
 * <code>javax.sql.RowSetReader</code> object the <code>RowSet</code> object
 * needs to populate itself with data and the
 * <code>javax.sql.RowSetWriter</code> object it needs to
 * propagate changes to its
 * data back to the underlying data source.
 * <P>
 * Because the methods in the <code>SyncFactory</code> class are all static,
 * there is only one <code>SyncFactory</code> object
 * per Java VM at any one time. This ensures that there is a single source from which a
 * <code>RowSet</code> implementation can obtain its <code>SyncProvider</code>
 * implementation.
 *
 * <h3>1.0 Overview</h3>
 * The <code>SyncFactory</code> class provides an internal registry of available
 * synchronization provider implementations (<code>SyncProvider</code> objects).
 * This registry may be queried to determine which
 * synchronization providers are available.
 * The following line of code gets an enumeration of the providers currently registered.
 * <PRE>
 * java.util.Enumeration e = SyncFactory.getRegisteredProviders();
 * </PRE>
 * All standard <code>RowSet</code> implementations must provide at least two providers:
 * <UL>
 * <LI>an optimistic provider for use with a <code>CachedRowSet</code> implementation
 * or an implementation derived from it
 * <LI>an XML provider, which is used for reading and writing XML, such as with
 * <code>WebRowSet</code> objects
 * </UL>
 * Note that the JDBC RowSet Implementations include the <code>SyncProvider</code>
 * implementations <code>RIOptimisticProvider</code> and <code>RIXmlProvider</code>,
 * which satisfy this requirement.
 * <P>
 * The <code>SyncFactory</code> class provides accessor methods to assist
 * applications in determining which synchronization providers are currently
 * registered with the <code>SyncFactory</code>.
 * <p>
 * Other methods let <code>RowSet</code> persistence providers be
 * registered or de-registered with the factory mechanism. This
 * allows additional synchronization provider implementations to be made
 * available to <code>RowSet</code> objects at run time.
 * <p>
 * Applications can apply a degree of filtering to determine the level of
 * synchronization that a <code>SyncProvider</code> implementation offers.
 * The following criteria determine whether a provider is
 * made available to a <code>RowSet</code> object:
 * <ol>
 * <li>If a particular provider is specified by a <code>RowSet</code> object, and
 * the <code>SyncFactory</code> does not contain a reference to this provider,
 * a <code>SyncFactoryException</code> is thrown stating that the synchronization
 * provider could not be found.
 *
 * <li>If a <code>RowSet</code> implementation is instantiated with a specified
 * provider and the specified provider has been properly registered, the
 * requested provider is supplied. Otherwise a <code>SyncFactoryException</code>
 * is thrown.
 *
 * <li>If a <code>RowSet</code> object does not specify a
 * <code>SyncProvider</code> implementation and no additional
 * <code>SyncProvider</code> implementations are available, the reference
 * implementation providers are supplied.
 * </ol>
 * <h3>2.0 Registering <code>SyncProvider</code> Implementations</h3>
 * <p>
 * Both vendors and developers can register <code>SyncProvider</code>
 * implementations using one of the following mechanisms.
 * <ul>
 * <LI><B>Using the command line</B><BR>
 * The name of the provider is supplied on the command line, which will add
 * the provider to the system properties.
 * For example:
 * <PRE>
 * -Drowset.provider.classname=com.fred.providers.HighAvailabilityProvider
 * </PRE>
 * <li><b>Using the Standard Properties File</b><BR>
 * The reference implementation is targeted
 * to ship with J2SE 1.5, which will include an additional resource file
 * that may be edited by hand. Here is an example of the properties file
 * included in the reference implementation:
 * <PRE>
 * #Default JDBC RowSet sync providers listing
 * #
 *
 * # Optimistic synchronization provider
 * rowset.provider.classname.0=com.sun.rowset.providers.RIOptimisticProvider
 * rowset.provider.vendor.0=Oracle Corporation
 * rowset.provider.version.0=1.0
 *
 * # XML Provider using standard XML schema
 * rowset.provider.classname.1=com.sun.rowset.providers.RIXMLProvider
 * rowset.provider.vendor.1=Oracle Corporation
 * rowset.provider.version.1=1.0
 * </PRE>
 * The <code>SyncFactory</code> checks this file and registers the
 * <code>SyncProvider</code> implementations that it contains. A
 * developer or vendor can add other implementations to this file.
 * For example, here is a possible addition:
 * <PRE>
 * rowset.provider.classname.2=com.fred.providers.HighAvailabilityProvider
 * rowset.provider.vendor.2=Fred, Inc.
 * rowset.provider.version.2=1.0
 * </PRE>
 *
 * <li><b>Using a JNDI Context</b><BR>
 * Available providers can be registered on a JNDI
 * context, and the <code>SyncFactory</code> will attempt to load
 * <code>SyncProvider</code> implementations from that JNDI context.
 * For example, the following code fragment registers a provider implementation
 * on a JNDI context.  This is something a deployer would normally do. In this
 * example, <code>MyProvider</code> is being registered on a CosNaming
 * namespace, which is the namespace used by J2EE resources.
 * <PRE>
 * import javax.naming.*;
 *
 * Hashtable svrEnv = new  Hashtable();
 * srvEnv.put(Context.INITIAL_CONTEXT_FACTORY, "CosNaming");
 *
 * Context ctx = new InitialContext(svrEnv);
 * com.fred.providers.MyProvider = new MyProvider();
 * ctx.rebind("providers/MyProvider", syncProvider);
 * </PRE>
 * </ul>
 * Next, an application will register the JNDI context with the
 * <code>SyncFactory</code> instance.  This allows the <code>SyncFactory</code>
 * to browse within the JNDI context looking for <code>SyncProvider</code>
 * implementations.
 * <PRE>
 * Hashtable appEnv = new Hashtable();
 * appEnv.put(Context.INITIAL_CONTEXT_FACTORY, "CosNaming");
 * appEnv.put(Context.PROVIDER_URL, "iiop://hostname/providers");
 * Context ctx = new InitialContext(appEnv);
 *
 * SyncFactory.registerJNDIContext(ctx);
 * </PRE>
 * If a <code>RowSet</code> object attempts to obtain a <code>MyProvider</code>
 * object, the <code>SyncFactory</code> will try to locate it. First it searches
 * for it in the system properties, then it looks in the resource files, and
 * finally it checks the JNDI context that has been set. The <code>SyncFactory</code>
 * instance verifies that the requested provider is a valid extension of the
 * <code>SyncProvider</code> abstract class and then gives it to the
 * <code>RowSet</code> object. In the following code fragment, a new
 * <code>CachedRowSet</code> object is created and initialized with
 * <i>env</i>, which contains the binding to <code>MyProvider</code>.
 * <PRE>
 * Hashtable env = new Hashtable();
 * env.put(SyncFactory.ROWSET_SYNC_PROVIDER, "com.fred.providers.MyProvider");
 * CachedRowSet crs = new com.sun.rowset.CachedRowSetImpl(env);
 * </PRE>
 * Further details on these mechanisms are available in the
 * <code>javax.sql.rowset.spi</code> package specification.
 *
 * @author Jonathan Bruce
 * @see javax.sql.rowset.spi.SyncProvider
 * @see javax.sql.rowset.spi.SyncFactoryException
 */
public class SyncFactory {

  /**
   * Creates a new <code>SyncFactory</code> object, which is the singleton
   * instance.
   * Having a private constructor guarantees that no more than
   * one <code>SyncProvider</code> object can exist at a time.
   */
  private SyncFactory() {
  }

  /**
   * The standard property-id for a synchronization provider implementation
   * name.
   */
  public static final String ROWSET_SYNC_PROVIDER =
      "rowset.provider.classname";
  /**
   * The standard property-id for a synchronization provider implementation
   * vendor name.
   */
  public static final String ROWSET_SYNC_VENDOR =
      "rowset.provider.vendor";
  /**
   * The standard property-id for a synchronization provider implementation
   * version tag.
   */
  public static final String ROWSET_SYNC_PROVIDER_VERSION =
      "rowset.provider.version";
  /**
   * The standard resource file name.
   */
  private static String ROWSET_PROPERTIES = "rowset.properties";

  /**
   * Permission required to invoke setJNDIContext and setLogger
   */
  private static final SQLPermission SET_SYNCFACTORY_PERMISSION =
      new SQLPermission("setSyncFactory");
  /**
   * The initial JNDI context where <code>SyncProvider</code> implementations can
   * be stored and from which they can be invoked.
   */
  private static Context ic;
  /**
   * The <code>Logger</code> object to be used by the <code>SyncFactory</code>.
   */
  private static volatile Logger rsLogger;

  /**
   * The registry of available <code>SyncProvider</code> implementations.
   * See section 2.0 of the class comment for <code>SyncFactory</code> for an
   * explanation of how a provider can be added to this registry.
   */
  private static Hashtable<String, SyncProvider> implementations;

  /**
   * Adds the the given synchronization provider to the factory register. Guidelines
   * are provided in the <code>SyncProvider</code> specification for the
   * required naming conventions for <code>SyncProvider</code>
   * implementations.
   * <p>
   * Synchronization providers bound to a JNDI context can be
   * registered by binding a SyncProvider instance to a JNDI namespace.
   *
   * <pre>
   * {@code
   * SyncProvider p = new MySyncProvider();
   * InitialContext ic = new InitialContext();
   * ic.bind ("jdbc/rowset/MySyncProvider", p);
   * } </pre>
   *
   * Furthermore, an initial JNDI context should be set with the
   * <code>SyncFactory</code> using the <code>setJNDIContext</code> method.
   * The <code>SyncFactory</code> leverages this context to search for
   * available <code>SyncProvider</code> objects bound to the JNDI
   * context and its child nodes.
   *
   * @param providerID A <code>String</code> object with the unique ID of the synchronization
   * provider being registered
   * @throws SyncFactoryException if an attempt is made to supply an empty or null provider name
   * @see #setJNDIContext
   */
  public static synchronized void registerProvider(String providerID)
      throws SyncFactoryException {

    ProviderImpl impl = new ProviderImpl();
    impl.setClassname(providerID);
    initMapIfNecessary();
    implementations.put(providerID, impl);

  }

  /**
   * Returns the <code>SyncFactory</code> singleton.
   *
   * @return the <code>SyncFactory</code> instance
   */
  public static SyncFactory getSyncFactory() {
        /*
         * Using Initialization on Demand Holder idiom as
         * Effective Java 2nd Edition,ITEM 71, indicates it is more performant
         * than the Double-Check Locking idiom.
         */
    return SyncFactoryHolder.factory;
  }

  /**
   * Removes the designated currently registered synchronization provider from the
   * Factory SPI register.
   *
   * @param providerID The unique-id of the synchronization provider
   * @throws SyncFactoryException If an attempt is made to unregister a SyncProvider implementation
   * that was not registered.
   */
  public static synchronized void unregisterProvider(String providerID)
      throws SyncFactoryException {
    initMapIfNecessary();
    if (implementations.containsKey(providerID)) {
      implementations.remove(providerID);
    }
  }

  private static String colon = ":";
  private static String strFileSep = "/";

  private static synchronized void initMapIfNecessary() throws SyncFactoryException {

    // Local implementation class names and keys from Properties
    // file, translate names into Class objects using Class.forName
    // and store mappings
    final Properties properties = new Properties();

    if (implementations == null) {
      implementations = new Hashtable<>();

      try {

        // check if user is supplying his Synchronisation Provider
        // Implementation if not using Oracle's implementation.
        // properties.load(new FileInputStream(ROWSET_PROPERTIES));

        // The rowset.properties needs to be in jdk/jre/lib when
        // integrated with jdk.
        // else it should be picked from -D option from command line.

        // -Drowset.properties will add to standard properties. Similar
        // keys will over-write

                /*
                 * Dependent on application
                 */
        String strRowsetProperties;
        try {
          strRowsetProperties = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
              return System.getProperty("rowset.properties");
            }
          }, null, new PropertyPermission("rowset.properties", "read"));
        } catch (Exception ex) {
          System.out.println("errorget rowset.properties: " + ex);
          strRowsetProperties = null;
        }
        ;

        if (strRowsetProperties != null) {
          // Load user's implementation of SyncProvider
          // here. -Drowset.properties=/abc/def/pqr.txt
          ROWSET_PROPERTIES = strRowsetProperties;
          try (FileInputStream fis = new FileInputStream(ROWSET_PROPERTIES)) {
            properties.load(fis);
          }
          parseProperties(properties);
        }

                /*
                 * Always available
                 */
        ROWSET_PROPERTIES = "javax" + strFileSep + "sql" +
            strFileSep + "rowset" + strFileSep +
            "rowset.properties";

        ClassLoader cl = Thread.currentThread().getContextClassLoader();

        try {
          AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
            try (InputStream stream = (cl == null) ?
                ClassLoader.getSystemResourceAsStream(ROWSET_PROPERTIES)
                : cl.getResourceAsStream(ROWSET_PROPERTIES)) {
              if (stream == null) {
                throw new SyncFactoryException("Resource " + ROWSET_PROPERTIES + " not found");
              }
              properties.load(stream);
            }
            return null;
          });
        } catch (PrivilegedActionException ex) {
          Throwable e = ex.getException();
          if (e instanceof SyncFactoryException) {
            throw (SyncFactoryException) e;
          } else {
            SyncFactoryException sfe = new SyncFactoryException();
            sfe.initCause(ex.getException());
            throw sfe;
          }
        }

        parseProperties(properties);

        // removed else, has properties should sum together

      } catch (FileNotFoundException e) {
        throw new SyncFactoryException("Cannot locate properties file: " + e);
      } catch (IOException e) {
        throw new SyncFactoryException("IOException: " + e);
      }

            /*
             * Now deal with -Drowset.provider.classname
             * load additional properties from -D command line
             */
      properties.clear();
      String providerImpls;
      try {
        providerImpls = AccessController.doPrivileged(new PrivilegedAction<String>() {
          public String run() {
            return System.getProperty(ROWSET_SYNC_PROVIDER);
          }
        }, null, new PropertyPermission(ROWSET_SYNC_PROVIDER, "read"));
      } catch (Exception ex) {
        providerImpls = null;
      }

      if (providerImpls != null) {
        int i = 0;
        if (providerImpls.indexOf(colon) > 0) {
          StringTokenizer tokenizer = new StringTokenizer(providerImpls, colon);
          while (tokenizer.hasMoreElements()) {
            properties.put(ROWSET_SYNC_PROVIDER + "." + i, tokenizer.nextToken());
            i++;
          }
        } else {
          properties.put(ROWSET_SYNC_PROVIDER, providerImpls);
        }
        parseProperties(properties);
      }
    }
  }

  /**
   * The internal debug switch.
   */
  private static boolean debug = false;
  /**
   * Internal registry count for the number of providers contained in the
   * registry.
   */
  private static int providerImplIndex = 0;

  /**
   * Internal handler for all standard property parsing. Parses standard
   * ROWSET properties and stores lazy references into the the internal registry.
   */
  private static void parseProperties(Properties p) {

    ProviderImpl impl = null;
    String key = null;
    String[] propertyNames = null;

    for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements(); ) {

      String str = (String) e.nextElement();

      int w = str.length();

      if (str.startsWith(SyncFactory.ROWSET_SYNC_PROVIDER)) {

        impl = new ProviderImpl();
        impl.setIndex(providerImplIndex++);

        if (w == (SyncFactory.ROWSET_SYNC_PROVIDER).length()) {
          // no property index has been set.
          propertyNames = getPropertyNames(false);
        } else {
          // property index has been set.
          propertyNames = getPropertyNames(true, str.substring(w - 1));
        }

        key = p.getProperty(propertyNames[0]);
        impl.setClassname(key);
        impl.setVendor(p.getProperty(propertyNames[1]));
        impl.setVersion(p.getProperty(propertyNames[2]));
        implementations.put(key, impl);
      }
    }
  }

  /**
   * Used by the parseProperties methods to disassemble each property tuple.
   */
  private static String[] getPropertyNames(boolean append) {
    return getPropertyNames(append, null);
  }

  /**
   * Disassembles each property and its associated value. Also handles
   * overloaded property names that contain indexes.
   */
  private static String[] getPropertyNames(boolean append,
      String propertyIndex) {
    String dot = ".";
    String[] propertyNames =
        new String[]{SyncFactory.ROWSET_SYNC_PROVIDER,
            SyncFactory.ROWSET_SYNC_VENDOR,
            SyncFactory.ROWSET_SYNC_PROVIDER_VERSION};
    if (append) {
      for (int i = 0; i < propertyNames.length; i++) {
        propertyNames[i] = propertyNames[i] +
            dot +
            propertyIndex;
      }
      return propertyNames;
    } else {
      return propertyNames;
    }
  }

  /**
   * Internal debug method that outputs the registry contents.
   */
  private static void showImpl(ProviderImpl impl) {
    System.out.println("Provider implementation:");
    System.out.println("Classname: " + impl.getClassname());
    System.out.println("Vendor: " + impl.getVendor());
    System.out.println("Version: " + impl.getVersion());
    System.out.println("Impl index: " + impl.getIndex());
  }

  /**
   * Returns the <code>SyncProvider</code> instance identified by <i>providerID</i>.
   *
   * @param providerID the unique identifier of the provider
   * @return a <code>SyncProvider</code> implementation
   * @throws SyncFactoryException If the SyncProvider cannot be found, the providerID is {@code
   * null}, or some error was encountered when trying to invoke this provider.
   */
  public static SyncProvider getInstance(String providerID)
      throws SyncFactoryException {

    if (providerID == null) {
      throw new SyncFactoryException("The providerID cannot be null");
    }

    initMapIfNecessary(); // populate HashTable
    initJNDIContext();    // check JNDI context for any additional bindings

    ProviderImpl impl = (ProviderImpl) implementations.get(providerID);

    if (impl == null) {
      // Requested SyncProvider is unavailable. Return default provider.
      return new com.sun.rowset.providers.RIOptimisticProvider();
    }

    try {
      ReflectUtil.checkPackageAccess(providerID);
    } catch (java.security.AccessControlException e) {
      SyncFactoryException sfe = new SyncFactoryException();
      sfe.initCause(e);
      throw sfe;
    }

    // Attempt to invoke classname from registered SyncProvider list
    Class<?> c = null;
    try {
      ClassLoader cl = Thread.currentThread().getContextClassLoader();

      /**
       * The SyncProvider implementation of the user will be in
       * the classpath. We need to find the ClassLoader which loads
       * this SyncFactory and try to load the SyncProvider class from
       * there.
       **/
      c = Class.forName(providerID, true, cl);

      if (c != null) {
        return (SyncProvider) c.newInstance();
      } else {
        return new com.sun.rowset.providers.RIOptimisticProvider();
      }

    } catch (IllegalAccessException e) {
      throw new SyncFactoryException("IllegalAccessException: " + e.getMessage());
    } catch (InstantiationException e) {
      throw new SyncFactoryException("InstantiationException: " + e.getMessage());
    } catch (ClassNotFoundException e) {
      throw new SyncFactoryException("ClassNotFoundException: " + e.getMessage());
    }
  }

  /**
   * Returns an Enumeration of currently registered synchronization
   * providers.  A <code>RowSet</code> implementation may use any provider in
   * the enumeration as its <code>SyncProvider</code> object.
   * <p>
   * At a minimum, the reference synchronization provider allowing
   * RowSet content data to be stored using a JDBC driver should be
   * possible.
   *
   * @return Enumeration  A enumeration of available synchronization providers that are registered
   * with this Factory
   * @throws SyncFactoryException If an error occurs obtaining the registered providers
   */
  public static Enumeration<SyncProvider> getRegisteredProviders()
      throws SyncFactoryException {
    initMapIfNecessary();
    // return a collection of classnames
    // of type SyncProvider
    return implementations.elements();
  }

  /**
   * Sets the logging object to be used by the <code>SyncProvider</code>
   * implementation provided by the <code>SyncFactory</code>. All
   * <code>SyncProvider</code> implementations can log their events to
   * this object and the application can retrieve a handle to this
   * object using the <code>getLogger</code> method.
   * <p>
   * This method checks to see that there is an {@code SQLPermission}
   * object  which grants the permission {@code setSyncFactory}
   * before allowing the method to succeed.  If a
   * {@code SecurityManager} exists and its
   * {@code checkPermission} method denies calling {@code setLogger},
   * this method throws a
   * {@code java.lang.SecurityException}.
   *
   * @param logger A Logger object instance
   * @throws java.lang.SecurityException if a security manager exists and its {@code
   * checkPermission} method denies calling {@code setLogger}
   * @throws NullPointerException if the logger is null
   * @see SecurityManager#checkPermission
   */
  public static void setLogger(Logger logger) {

    SecurityManager sec = System.getSecurityManager();
    if (sec != null) {
      sec.checkPermission(SET_SYNCFACTORY_PERMISSION);
    }

    if (logger == null) {
      throw new NullPointerException("You must provide a Logger");
    }
    rsLogger = logger;
  }

  /**
   * Sets the logging object that is used by <code>SyncProvider</code>
   * implementations provided by the <code>SyncFactory</code> SPI. All
   * <code>SyncProvider</code> implementations can log their events
   * to this object and the application can retrieve a handle to this
   * object using the <code>getLogger</code> method.
   * <p>
   * This method checks to see that there is an {@code SQLPermission}
   * object  which grants the permission {@code setSyncFactory}
   * before allowing the method to succeed.  If a
   * {@code SecurityManager} exists and its
   * {@code checkPermission} method denies calling {@code setLogger},
   * this method throws a
   * {@code java.lang.SecurityException}.
   *
   * @param logger a Logger object instance
   * @param level a Level object instance indicating the degree of logging required
   * @throws java.lang.SecurityException if a security manager exists and its {@code
   * checkPermission} method denies calling {@code setLogger}
   * @throws NullPointerException if the logger is null
   * @see SecurityManager#checkPermission
   * @see LoggingPermission
   */
  public static void setLogger(Logger logger, Level level) {
    // singleton
    SecurityManager sec = System.getSecurityManager();
    if (sec != null) {
      sec.checkPermission(SET_SYNCFACTORY_PERMISSION);
    }

    if (logger == null) {
      throw new NullPointerException("You must provide a Logger");
    }
    logger.setLevel(level);
    rsLogger = logger;
  }

  /**
   * Returns the logging object for applications to retrieve
   * synchronization events posted by SyncProvider implementations.
   *
   * @return The {@code Logger} that has been specified for use by {@code SyncProvider}
   * implementations
   * @throws SyncFactoryException if no logging object has been set.
   */
  public static Logger getLogger() throws SyncFactoryException {

    Logger result = rsLogger;
    // only one logger per session
    if (result == null) {
      throw new SyncFactoryException("(SyncFactory) : No logger has been set");
    }

    return result;
  }

  /**
   * Sets the initial JNDI context from which SyncProvider implementations
   * can be retrieved from a JNDI namespace
   * <p>
   * This method checks to see that there is an {@code SQLPermission}
   * object  which grants the permission {@code setSyncFactory}
   * before allowing the method to succeed.  If a
   * {@code SecurityManager} exists and its
   * {@code checkPermission} method denies calling {@code setJNDIContext},
   * this method throws a
   * {@code java.lang.SecurityException}.
   *
   * @param ctx a valid JNDI context
   * @throws SyncFactoryException if the supplied JNDI context is null
   * @throws java.lang.SecurityException if a security manager exists and its {@code
   * checkPermission} method denies calling {@code setJNDIContext}
   * @see SecurityManager#checkPermission
   */
  public static synchronized void setJNDIContext(javax.naming.Context ctx)
      throws SyncFactoryException {
    SecurityManager sec = System.getSecurityManager();
    if (sec != null) {
      sec.checkPermission(SET_SYNCFACTORY_PERMISSION);
    }
    if (ctx == null) {
      throw new SyncFactoryException("Invalid JNDI context supplied");
    }
    ic = ctx;
  }

  /**
   * Controls JNDI context initialization.
   *
   * @throws SyncFactoryException if an error occurs parsing the JNDI context
   */
  private static synchronized void initJNDIContext() throws SyncFactoryException {

    if ((ic != null) && (lazyJNDICtxRefresh == false)) {
      try {
        parseProperties(parseJNDIContext());
        lazyJNDICtxRefresh = true; // touch JNDI namespace once.
      } catch (NamingException e) {
        e.printStackTrace();
        throw new SyncFactoryException("SPI: NamingException: " + e.getExplanation());
      } catch (Exception e) {
        e.printStackTrace();
        throw new SyncFactoryException("SPI: Exception: " + e.getMessage());
      }
    }
  }

  /**
   * Internal switch indicating whether the JNDI namespace should be re-read.
   */
  private static boolean lazyJNDICtxRefresh = false;

  /**
   * Parses the set JNDI Context and passes bindings to the enumerateBindings
   * method when complete.
   */
  private static Properties parseJNDIContext() throws NamingException {

    NamingEnumeration<?> bindings = ic.listBindings("");
    Properties properties = new Properties();

    // Hunt one level below context for available SyncProvider objects
    enumerateBindings(bindings, properties);

    return properties;
  }

  /**
   * Scans each binding on JNDI context and determines if any binding is an
   * instance of SyncProvider, if so, add this to the registry and continue to
   * scan the current context using a re-entrant call to this method until all
   * bindings have been enumerated.
   */
  private static void enumerateBindings(NamingEnumeration<?> bindings,
      Properties properties) throws NamingException {

    boolean syncProviderObj = false; // move to parameters ?

    try {
      Binding bd = null;
      Object elementObj = null;
      String element = null;
      while (bindings.hasMore()) {
        bd = (Binding) bindings.next();
        element = bd.getName();
        elementObj = bd.getObject();

        if (!(ic.lookup(element) instanceof Context)) {
          // skip directories/sub-contexts
          if (ic.lookup(element) instanceof SyncProvider) {
            syncProviderObj = true;
          }
        }

        if (syncProviderObj) {
          SyncProvider sync = (SyncProvider) elementObj;
          properties.put(SyncFactory.ROWSET_SYNC_PROVIDER,
              sync.getProviderID());
          syncProviderObj = false; // reset
        }

      }
    } catch (javax.naming.NotContextException e) {
      bindings.next();
      // Re-entrant call into method
      enumerateBindings(bindings, properties);
    }
  }

  /**
   * Lazy initialization Holder class used by {@code getSyncFactory}
   */
  private static class SyncFactoryHolder {

    static final SyncFactory factory = new SyncFactory();
  }
}

/**
 * Internal class that defines the lazy reference construct for each registered
 * SyncProvider implementation.
 */
class ProviderImpl extends SyncProvider {

  private String className = null;
  private String vendorName = null;
  private String ver = null;
  private int index;

  public void setClassname(String classname) {
    className = classname;
  }

  public String getClassname() {
    return className;
  }

  public void setVendor(String vendor) {
    vendorName = vendor;
  }

  public String getVendor() {
    return vendorName;
  }

  public void setVersion(String providerVer) {
    ver = providerVer;
  }

  public String getVersion() {
    return ver;
  }

  public void setIndex(int i) {
    index = i;
  }

  public int getIndex() {
    return index;
  }

  public int getDataSourceLock() throws SyncProviderException {

    int dsLock = 0;
    try {
      dsLock = SyncFactory.getInstance(className).getDataSourceLock();
    } catch (SyncFactoryException sfEx) {

      throw new SyncProviderException(sfEx.getMessage());
    }

    return dsLock;
  }

  public int getProviderGrade() {

    int grade = 0;

    try {
      grade = SyncFactory.getInstance(className).getProviderGrade();
    } catch (SyncFactoryException sfEx) {
      //
    }

    return grade;
  }

  public String getProviderID() {
    return className;
  }

  /*
  public javax.sql.RowSetInternal getRowSetInternal() {
  try
  {
  return SyncFactory.getInstance(className).getRowSetInternal();
  } catch(SyncFactoryException sfEx) {
  //
  }
  }
   */
  public javax.sql.RowSetReader getRowSetReader() {

    RowSetReader rsReader = null;

    try {
      rsReader = SyncFactory.getInstance(className).getRowSetReader();
    } catch (SyncFactoryException sfEx) {
      //
    }

    return rsReader;

  }

  public javax.sql.RowSetWriter getRowSetWriter() {

    RowSetWriter rsWriter = null;
    try {
      rsWriter = SyncFactory.getInstance(className).getRowSetWriter();
    } catch (SyncFactoryException sfEx) {
      //
    }

    return rsWriter;
  }

  public void setDataSourceLock(int param)
      throws SyncProviderException {

    try {
      SyncFactory.getInstance(className).setDataSourceLock(param);
    } catch (SyncFactoryException sfEx) {

      throw new SyncProviderException(sfEx.getMessage());
    }
  }

  public int supportsUpdatableView() {

    int view = 0;

    try {
      view = SyncFactory.getInstance(className).supportsUpdatableView();
    } catch (SyncFactoryException sfEx) {
      //
    }

    return view;
  }
}
